﻿namespace Socket.Core
{
    using System;
    using System.Collections.Generic;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    using Socket.Core.Error;

    public class SocketClient : AbstractClient
    {
        private BufferManager m_BufferManager;
        private Socket m_Client;
        private SocketAsyncEventArgsPool m_SocketAsyncEventArgsPool;
        public int RetryCount;

        public SocketClient(IPackageCommand packageCommand) : base(packageCommand)
        {
        }

        private void BeginConnect(SocketAsyncEventArgs args)
        {
            if (args.SocketError == SocketError.Success)
            {
                base.ServerSession = new TcpSession(args);
                this.ServerSession.Released += new EventHandler(this.TcpSessionReleased);
                Socket acceptSocket = args.AcceptSocket;
                
                if (!this.m_BufferManager.SetBuffer(args))
                {
                    this.m_SocketAsyncEventArgsPool.Push(args);
                }
                if (!acceptSocket.ReceiveAsync(args))
                {
                    this.BeginReceive(args);
                }
                UnityEngine.Debug.Log("Connect server Success.");
                this.OnConnected(new SessionArgs(this.ServerSession));
            }
            else
            {
                this.m_SocketAsyncEventArgsPool.Push(args);
                this.RetryConnect();
            }
        }

        private void BeginReceive(SocketAsyncEventArgs args)
        {
            ISession serverSession = this.ServerSession;
            if (serverSession != null)
            {
                Socket acceptSocket = args.AcceptSocket;
                if (args.SocketError == SocketError.Success)
                {
                    if (args.BytesTransferred > 0)
                    {
                        if ((serverSession.ReceiveData != null) && (serverSession.ReceiveData.ReceiveTime < DateTime.Now.AddMilliseconds((double) -base.ClientConfig.ReceiveTimeout)))
                        {
                            serverSession.InitReceive();
                        }
                        try
                        {
                            List<IReceiveInfo> list = null;
                            try
                            {
                                list = this.PackageCommand.PartPackageCommand(serverSession, args.Buffer, args.Offset, args.BytesTransferred);
                            }
                            catch (ClientException exception)
                            {
                                if (exception.ExceptionType < ((ClientExceptionType) 100))
                                {
                                    serverSession.Release();
                                    return;
                                }
                            }
                            if ((list != null) && (list.Count > 0))
                            {
                                foreach (IReceiveInfo info in list)
                                {
                                    using (IReceiveInfo info2 = info)
                                    {
                                        if (info2.IsHeartbeat)
                                        {
                                            this.OnHeartbeatReceived(new HeartbeatReceivedArgs(serverSession));
                                        }
                                        else
                                        {
                                            this.OnReceived(new ReceivedArgs(serverSession, info2));
                                        }
                                    }
                                }
                            }
                        }
                        catch (Exception)
                        {
                        }
                        if (((acceptSocket != null) && acceptSocket.Connected) && !acceptSocket.ReceiveAsync(args))
                        {
                            this.BeginReceive(args);
                        }
                    }
                    else
                    {
                        serverSession.Release();
                    }
                }
                else
                {
                    serverSession.Release();
                }
            }
        }

        private Socket CreateSocket(IPEndPoint endpoint)
        {
            return new Socket(endpoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        }

        public override void DisConnect()
        {
            if ((this.m_Client != null) && this.m_Client.Connected)
            {
                this.m_Client.Shutdown(SocketShutdown.Both);
                this.m_Client.Close();
                this.m_Client = null;
                base.ClientConfig.RetryCount = 0;
                base.ClientConfig.RetryInterval = 0;
                UnityEngine.Debug.Log("Socket Close!");
            }
        }

        private void RetryConnect()
        {
            if ((base.ClientConfig.RetryInterval != 0) && ((base.ClientConfig.RetryCount == 0) || (base.ClientConfig.RetryCount >= this.RetryCount)))
            {
                this.RetryCount++;
                Thread.Sleep((int) (base.ClientConfig.RetryInterval * 0x3e8));
                this.StartConnect();
            }
        }

        public override void Setup(ClientConfig config)
        {
            if (config == null)
            {
                throw new ArgumentException("ServerConfig");
            }
            base.ClientConfig = config;
            this.PackageCommand.Config = config;
            this.m_Client = this.CreateSocket(config.EndPoint);
        }

        public override bool StartConnect()
        {
            if (this.RetryCount <= 0)
            {
                this.m_BufferManager = new BufferManager(base.ClientConfig.MaxConnection * base.ClientConfig.BuffSize, base.ClientConfig.BuffSize);
                this.m_BufferManager.Inint();
                this.m_SocketAsyncEventArgsPool = new SocketAsyncEventArgsPool(base.ClientConfig.MaxConnection);
                for (int i = 0; i < base.ClientConfig.MaxConnection; i++)
                {
                    SocketAsyncEventArgs item = new SocketAsyncEventArgs();
                    item.Completed += new EventHandler<SocketAsyncEventArgs>(this.TcpClientCompleted);
                    this.m_SocketAsyncEventArgsPool.Push(item);
                }
                UnityEngine.Debug.Log("Init socketAsyncEventArgsPool..");
            }

            SocketAsyncEventArgs e = this.m_SocketAsyncEventArgsPool.Pop();
            e.RemoteEndPoint = base.ClientConfig.EndPoint;
            e.AcceptSocket = this.m_Client;
            try
            {
                if (!this.m_Client.ConnectAsync(e))
                {
                    this.BeginConnect(e);
                }
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.Message);
                throw;
            }
            return true;
        }

        private void TcpClientCompleted(object sender, SocketAsyncEventArgs args)
        {
            switch (args.LastOperation)
            {
                case SocketAsyncOperation.Connect:
                    this.BeginConnect(args);
                    break;

                case SocketAsyncOperation.Receive:
                    this.BeginReceive(args);
                    break;
            }
        }

        private void TcpSessionReleased(object sender, EventArgs e)
        {
            TcpSession session = (TcpSession) sender;
            SocketAsyncEventArgs sAEAOrEndPoint = (SocketAsyncEventArgs) session.SAEAOrEndPoint;
            this.m_BufferManager.FreeBuffer(sAEAOrEndPoint);
            this.m_SocketAsyncEventArgsPool.Push(sAEAOrEndPoint);
            this.OnDisConnected(new SessionArgs(session));
            this.RetryCount = 0;
            this.Setup(base.ClientConfig);
            this.RetryConnect();
        }
    }
}

